package com.wg.net.client;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wg.core.lang.IDisposable;
import com.wg.core.utils.ByteBufUtils;
import com.wg.game.app.BaseApplication;
import com.wg.net.DisconnectReason;
import com.wg.net.NettyAttributeKey;
import com.wg.net.client.core.ConnectionState;
import com.wg.net.peer.AbstractOutboundServerToServerPeer;
import com.wg.net.session.ISession;
import com.wg.net.session.TcpSession;
import com.wg.net.codec.MyDecoder;
import com.wg.net.codec.MyEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollIoHandler;
import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.GenericFutureListener;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 服务器到服务器TCP客户端（基于Netty实现）
 * 
 * @author 少爷123
 */
public class ServerToServerClient implements IDisposable {
    private static final Log log = LogFactory.get();

    // 核心组件
    private final MultiThreadIoEventLoopGroup workerGroup;
    private final Bootstrap bootstrap;
    private final AbstractOutboundServerToServerPeer serverPeer;

    // 连接状态与资源
    private final AtomicReference<Channel> channelRef = new AtomicReference<>();
    private final AtomicReference<ISession> sessionRef = new AtomicReference<>();
    private final AtomicBoolean isDisposed = new AtomicBoolean(false);
    private final AtomicReference<ConnectionState> connectionState = new AtomicReference<>(
            ConnectionState.Disconnected);

    // 配置参数
    private final int connectionTimeoutMs;
    private final int idleTimeoutMs;

    public ServerToServerClient(int connectionTimeoutMs, int idleTimeoutMs,
            AbstractOutboundServerToServerPeer serverPeer) {
        this.connectionTimeoutMs = connectionTimeoutMs;
        this.idleTimeoutMs = idleTimeoutMs;
        this.serverPeer = serverPeer;

        final IoHandlerFactory ioFactory = Epoll.isAvailable() ? EpollIoHandler.newFactory()
                : NioIoHandler.newFactory();
        this.workerGroup = new MultiThreadIoEventLoopGroup(1, ioFactory);
        this.bootstrap = new Bootstrap();
        configureBootstrap();
    }

    private void configureBootstrap() {
        Class<? extends Channel> channelClass = Epoll.isAvailable()
                ? io.netty.channel.epoll.EpollSocketChannel.class
                : NioSocketChannel.class;
        bootstrap.group(workerGroup)
                .channel(channelClass)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutMs)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.WRITE_BUFFER_WATER_MARK,
                        new WriteBufferWaterMark(32 * 1024, 64 * 1024 * 100))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline pipeline = ch.pipeline();

                        pipeline.addLast(new MyDecoder());
                        pipeline.addLast(new MyEncoder());

                        if (idleTimeoutMs > 0) {
                            pipeline.addLast(new IdleStateHandler(
                                    0, 0, idleTimeoutMs, TimeUnit.MILLISECONDS));
                        }

                        pipeline.addLast(new ClientHeartBeatHandler());
                        pipeline.addLast(new MessageInboundHandler());
                    }
                });
    }

    /**
     * 发起连接（线程安全）
     */
    public void connect(String ip, int port) {
        // 尝试原子更新状态，如果不是 Disconnected 则返回
        if (!connectionState.compareAndSet(ConnectionState.Disconnected, ConnectionState.Connecting)) {
            log.warn("当前状态不允许连接 | 状态={}", connectionState.get());
            return;
        }

        if (isDisposed.get()) {
            log.warn("客户端已释放，无法连接");
            connectionState.set(ConnectionState.Disconnected);
            return;
        }

        log.info("尝试连接服务器 | IP={}, 端口={}", ip, port);
        bootstrap.connect(ip, port).addListener(new ConnectListener(ip, port));
    }

    @Override
    public void dispose() {
        // 先尝试标记已释放，避免重复释放
        if (!isDisposed.compareAndSet(false, true))
            return;

        log.info("开始释放客户端资源...");

        Channel ch = channelRef.getAndSet(null);
        ISession session = sessionRef.getAndSet(null);
        connectionState.set(ConnectionState.Disconnected);

        if (ch != null)
            closeChannel(ch);

        workerGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS)
                .addListener(future -> {
                    if (future.isSuccess())
                        log.info("客户端资源释放完成");
                    else
                        log.error("资源释放异常", future.cause());
                });

        if (session != null) {
            BaseApplication.INST.onDisconnect(session, serverPeer,
                    DisconnectReason.ClientDisconnect, "Resource disposed");
        }
    }

    private void closeChannel(Channel channel) {
        if (channel != null && channel.isActive()) {
            log.info("关闭通道 | 本地={}, 远程={}", channel.localAddress(), channel.remoteAddress());
            channel.close().addListener(future -> {
                if (!future.isSuccess()) {
                    log.error("通道关闭失败", future.cause());
                }
            });
        }
    }

    public ConnectionState getConnectionState() {
        return connectionState.get();
    }

    public boolean isConnected() {
        return connectionState.get() == ConnectionState.Connected;
    }

    /**
     * 连接结果监听器（分离回调逻辑）
     */
    private class ConnectListener implements GenericFutureListener<ChannelFuture> {
        private final String ip;
        private final int port;

        ConnectListener(String ip, int port) {
            this.ip = ip;
            this.port = port;
        }

        @Override
        public void operationComplete(ChannelFuture future) {
            final Channel ch = future.channel();

            if (isDisposed.get()) {
                log.warn("客户端已释放，忽略连接结果 | 远程={}", ch.remoteAddress());
                closeChannel(ch);
                return;
            }

            if (future.isSuccess()) {
                handleConnectSuccess(ch);
            } else {
                handleConnectFailure(future);
            }
        }

        private void handleConnectSuccess(Channel ch) {
            log.info("连接建立成功 | 本地={}, 远程={}", ch.localAddress(), ch.remoteAddress());
            channelRef.set(ch);
            TcpSession session = new TcpSession(ch);
            session.setUserData(serverPeer);
            sessionRef.set(session);
            connectionState.set(ConnectionState.Connected);

            // 业务回调移出同步块
            BaseApplication.INST.onOutboundConnectionEstablished(session, serverPeer);
        }

        private void handleConnectFailure(ChannelFuture future) {
            log.error("连接建立失败 | IP={}, 端口={}, 原因={}",
                    ip, port, future.cause().getMessage());
            connectionState.set(ConnectionState.Disconnected);

            // 业务回调移出同步块
            BaseApplication.INST.onOutboundConnectionFailed(
                    serverPeer, -1, "Connection failed: " + future.cause().getMessage());
            closeChannel(future.channel());
        }
    }

    private class MessageInboundHandler extends SimpleChannelInboundHandler<ByteBuf> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf data) {
            try {
                byte[] buffer = ByteBufUtils.readBytes(data);
                ISession session = sessionRef.get();
                if (session != null && session.getUserData() != null) {
                    BaseApplication.INST.onReceive(session, session.getUserData(), buffer);
                }
            } catch (Exception e) {
                log.error("消息处理异常 | 通道={}", ctx.channel(), e);
                ctx.close();
            }
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            log.info("通道变为非活跃状态 | 本地={}, 远程={}",
                    ctx.channel().localAddress(), ctx.channel().remoteAddress());

            final ISession session = sessionRef.get();
            if (session == null) {
                log.warn("通道已关闭，但未找到对应的会话");
                return;
            }

            final DisconnectReason reason = ctx.channel()
                    .attr(NettyAttributeKey.DISCONNECT_REASON)
                    .getAndSet(DisconnectReason.ClientDisconnect);

            connectionState.set(ConnectionState.Disconnected);

            // 业务回调移出同步块
            BaseApplication.INST.onDisconnect(session, session.getUserData(),
                    reason, reason.getDesc());
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            log.error("通道异常 | 本地={}, 远程={}, 原因={}",
                    ctx.channel().localAddress(), ctx.channel().remoteAddress(),
                    cause.getMessage(), cause);
            ctx.close();
        }
    }

    private static class ClientHeartBeatHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
            if (evt instanceof IdleStateEvent) {
                IdleState state = ((IdleStateEvent) evt).state();
                if (state == IdleState.READER_IDLE) {
                    log.warn("读空闲超时，关闭连接 | 本地={}, 远程={}",
                            ctx.channel().localAddress(), ctx.channel().remoteAddress());
                    ctx.channel().attr(NettyAttributeKey.DISCONNECT_REASON)
                            .set(DisconnectReason.TimeoutDisconnect);
                    ctx.close();
                }
            }
        }
    }
}